"""
安全相关工具函数
包括密码哈希、JWT令牌处理等
"""

import secrets
import hashlib
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from passlib.context import CryptContext
from jose import JWTError, jwt
from fastapi import HTTPException, status
from app.core.config import settings
from app.utils.timezone_utils import get_shanghai_now


# 密码上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码"""
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    """生成密码哈希"""
    return pwd_context.hash(password)


def generate_password_reset_token(email: str) -> str:
    """生成密码重置令牌"""
    delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
    now = get_shanghai_now()
    expires = now + delta
    exp = expires.timestamp()
    encoded_jwt = jwt.encode(
        {"exp": exp, "nbf": now, "sub": email}, 
        settings.SECRET_KEY, 
        algorithm="HS256"
    )
    return encoded_jwt


def verify_password_reset_token(token: str) -> Optional[str]:
    """验证密码重置令牌"""
    try:
        decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
        return decoded_token["sub"]
    except JWTError:
        return None


def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
    """创建访问令牌"""
    to_encode = data.copy()
    if expires_delta:
        expire = get_shanghai_now() + expires_delta
    else:
        expire = get_shanghai_now() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    
    to_encode.update({"exp": expire, "type": "access"})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt


def create_refresh_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
    """创建刷新令牌"""
    to_encode = data.copy()
    if expires_delta:
        expire = get_shanghai_now() + expires_delta
    else:
        expire = get_shanghai_now() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
    
    to_encode.update({"exp": expire, "type": "refresh"})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt


def verify_token(token: str, token_type: str = "access") -> Optional[Dict[str, Any]]:
    """验证令牌"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        
        # 检查令牌类型
        if payload.get("type") != token_type:
            return None
            
        # 检查过期时间
        exp = payload.get("exp")
        if exp is None or datetime.fromtimestamp(exp) < get_shanghai_now():
            return None
            
        return payload
    except JWTError:
        return None


def generate_session_token() -> str:
    """生成会话令牌"""
    return secrets.token_urlsafe(32)


def generate_api_key() -> str:
    """生成API密钥"""
    return secrets.token_urlsafe(32)


def hash_token(token: str) -> str:
    """对令牌进行哈希处理"""
    return hashlib.sha256(token.encode()).hexdigest()


def validate_password_strength(password: str) -> Dict[str, Any]:
    """验证密码强度"""
    result = {
        "is_valid": True,
        "score": 0,
        "feedback": [],
        "requirements": {
            "length": len(password) >= 8,
            "uppercase": any(c.isupper() for c in password),
            "lowercase": any(c.islower() for c in password),
            "digit": any(c.isdigit() for c in password),
            "special": any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in password)
        }
    }
    
    # 检查长度
    if len(password) < 8:
        result["feedback"].append("密码长度至少8个字符")
        result["is_valid"] = False
    elif len(password) >= 12:
        result["score"] += 2
    else:
        result["score"] += 1
    
    # 检查字符类型
    if not result["requirements"]["uppercase"]:
        result["feedback"].append("密码必须包含至少一个大写字母")
        result["is_valid"] = False
    else:
        result["score"] += 1
    
    if not result["requirements"]["lowercase"]:
        result["feedback"].append("密码必须包含至少一个小写字母")
        result["is_valid"] = False
    else:
        result["score"] += 1
    
    if not result["requirements"]["digit"]:
        result["feedback"].append("密码必须包含至少一个数字")
        result["is_valid"] = False
    else:
        result["score"] += 1
    
    if not result["requirements"]["special"]:
        result["feedback"].append("密码必须包含至少一个特殊字符")
        result["is_valid"] = False
    else:
        result["score"] += 1
    
    # 检查常见弱密码
    common_passwords = [
        "password", "123456", "123456789", "qwerty", "abc123",
        "password123", "admin", "root", "user", "test"
    ]
    if password.lower() in common_passwords:
        result["feedback"].append("不能使用常见的弱密码")
        result["is_valid"] = False
        result["score"] = max(0, result["score"] - 2)
    
    # 检查重复字符
    if len(set(password)) < len(password) * 0.6:
        result["feedback"].append("密码包含过多重复字符")
        result["score"] = max(0, result["score"] - 1)
    
    # 计算强度等级
    if result["score"] >= 6:
        result["strength"] = "很强"
    elif result["score"] >= 4:
        result["strength"] = "强"
    elif result["score"] >= 2:
        result["strength"] = "中等"
    else:
        result["strength"] = "弱"
    
    return result


def check_user_locked(user) -> bool:
    """检查用户是否被锁定"""
    if user.locked_until and user.locked_until > get_shanghai_now():
        return True
    return False


def should_lock_user(login_attempts: int) -> bool:
    """判断是否应该锁定用户"""
    return login_attempts >= settings.MAX_LOGIN_ATTEMPTS


def calculate_lock_duration(login_attempts: int) -> timedelta:
    """计算锁定时长"""
    # 递增锁定时间：5分钟、15分钟、30分钟、1小时、2小时...
    base_minutes = 5
    multiplier = min(login_attempts - settings.MAX_LOGIN_ATTEMPTS + 1, 8)
    minutes = base_minutes * (2 ** (multiplier - 1))
    return timedelta(minutes=minutes)


def generate_csrf_token() -> str:
    """生成CSRF令牌"""
    return secrets.token_urlsafe(32)


def verify_csrf_token(token: str, expected_token: str) -> bool:
    """验证CSRF令牌"""
    return secrets.compare_digest(token, expected_token)


def sanitize_filename(filename: str) -> str:
    """清理文件名，防止路径遍历攻击"""
    import os
    import re
    
    # 移除路径分隔符
    filename = os.path.basename(filename)
    
    # 移除危险字符
    filename = re.sub(r'[<>:"/\\|?*]', '', filename)
    
    # 限制长度
    if len(filename) > 255:
        name, ext = os.path.splitext(filename)
        filename = name[:255-len(ext)] + ext
    
    return filename


def validate_ip_address(ip: str) -> bool:
    """验证IP地址格式"""
    import ipaddress
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError:
        return False


def mask_sensitive_data(data: str, mask_char: str = "*", visible_chars: int = 4) -> str:
    """遮蔽敏感数据"""
    if len(data) <= visible_chars:
        return mask_char * len(data)
    
    return data[:visible_chars] + mask_char * (len(data) - visible_chars)


def generate_verification_code(length: int = 6) -> str:
    """生成验证码"""
    import random
    import string
    
    characters = string.digits
    return ''.join(random.choice(characters) for _ in range(length))


class SecurityHeaders:
    """安全头部工具类"""
    
    @staticmethod
    def get_security_headers() -> Dict[str, str]:
        """获取安全头部"""
        return {
            "X-Content-Type-Options": "nosniff",
            "X-Frame-Options": "DENY",
            "X-XSS-Protection": "1; mode=block",
            "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
            "Content-Security-Policy": "default-src 'self'",
            "Referrer-Policy": "strict-origin-when-cross-origin"
        }


def rate_limit_key(identifier: str, action: str) -> str:
    """生成限流键"""
    return f"rate_limit:{action}:{identifier}"


def get_client_ip(request) -> str:
    """获取客户端IP地址"""
    # 检查代理头部
    forwarded_for = request.headers.get("X-Forwarded-For")
    if forwarded_for:
        return forwarded_for.split(",")[0].strip()
    
    real_ip = request.headers.get("X-Real-IP")
    if real_ip:
        return real_ip
    
    return request.client.host if request.client else "unknown"
